package it.uniroma2.art.coda.core;

import it.uniroma2.art.coda.projectionrule.PlaceholderStruct;

import java.util.ArrayList;
import java.util.List;

import org.apache.uima.cas.Feature;
import org.apache.uima.cas.FeatureStructure;
import org.apache.uima.cas.Type;
import org.apache.uima.jcas.cas.BooleanArray;
import org.apache.uima.jcas.cas.DoubleArray;
import org.apache.uima.jcas.cas.FSArray;
import org.apache.uima.jcas.cas.FSList;
import org.apache.uima.jcas.cas.FloatArray;
import org.apache.uima.jcas.cas.IntegerArray;
import org.apache.uima.jcas.cas.LongArray;
import org.apache.uima.jcas.cas.NonEmptyFSList;
import org.apache.uima.jcas.cas.NonEmptyStringList;
import org.apache.uima.jcas.cas.StringArray;
import org.apache.uima.jcas.cas.StringList;
import org.apache.uima.jcas.tcas.Annotation;

/**
 * This class contains some methid that can be used as utilities regarding the use of UIMA in CODA
 * 
 * @author Andrea Turbati
 * 
 */
public class UIMACODAUtilities {

	private String STRINGARRAY = "uima.cas.StringArray";
	private String INTEGERARRAY = "uima.cas.IntegerArray";
	private String LONGARRAY = "uima.cas.LongArray";
	private String BOOLEANARRAY = "uima.cas.BooleanArray";
	private String DOUBLEARRAY = "uima.cas.DoubleArray";
	private String FLOATARRAY = "uima.cas.FloatArray";
	private String FSARRAY = "uima.cas.FSArray";
	private String NONEMPTYSTRINGLIST = "uima.cas.NonEmptyStringList";
	private String STRINGLIST = "uima.cas.StringList";
	private String NONEMPTYFSLIST = "uima.cas.NonEmptyFSList";
	private String FSLIST = "uima.cas.FSList";

	/**
	 * Get a value (normally the first if more than one is present) in a particular annotation following the
	 * feature path contained in the placeholder
	 * 
	 * @param ann
	 *            the annotation where the value will be taken from
	 * @param placeholder
	 *            the placeholder containing the feature path
	 * @return the one (just one) corresponding to the feature path or null if there is no value
	 */
	public String getValueOfFeatureFromFeatPathOneValue(Annotation ann, PlaceholderStruct placeholder) {
		String uimaTypeAndFeat = placeholder.getTypeAndFeat();
		return getValueOfFeatureFromFeatPathOneValue(ann, uimaTypeAndFeat);
	}

	/**
	 * Get a value (normally the first if more than one is present) in a particular annotation following the
	 * feature path represented by uimaTypeAndFeat
	 * 
	 * @param ann
	 *            the annotation where the value will be taken from
	 * @param uimaTypeAndFeat
	 *            the feature path
	 * @return the one (just one) corresponding to the feature path or null if there is no value
	 */
	public String getValueOfFeatureFromFeatPathOneValue(Annotation ann, String uimaTypeAndFeat) {
		List<String> valueList = getValueOfFeatureFromFeatPath(ann, uimaTypeAndFeat);
		if (valueList == null)
			return null;
		else
			return valueList.get(0);
	}

	/**
	 * Get a list of values in a particular annotation following the feature path contained in the placeholder
	 * 
	 * @param ann
	 *            the annotation where the value will be taken from
	 * @param placeholder
	 *            the placeholder containing the feature path
	 * @return the list of values corresponding to the feature path or null if there is no value
	 */
	public List<String> getValueOfFeatureFromFeatPath(Annotation ann, PlaceholderStruct placeholder) {
		String uimaTypeAndFeat = placeholder.getTypeAndFeat();
		return getValueOfFeatureFromFeatPath(ann, uimaTypeAndFeat);
	}

	/**
	 * Get a list of values in a particular annotation following the feature path represented by
	 * uimaTypeAndFeat
	 * 
	 * @param ann
	 *            the annotation where the value will be taken from
	 * @param uimaTypeAndFeat
	 *            the feature path
	 * @return the list of values corresponding to the feature path or null if there is no value
	 */
	public List<String> getValueOfFeatureFromFeatPath(Annotation ann, String uimaTypeAndFeat) {

		List<String> featurePathValueList = new ArrayList<String>();

		if (uimaTypeAndFeat == "")
			return null;

		// split the uimaTypeAndFeat first with : (Type And Features), then the Features with / (path)
		String[] typeAndFeatArray = uimaTypeAndFeat.split(":");

		// check if there is a feature path or if there is just the uima type
		if (typeAndFeatArray.length == 1) {
			// there is no featurepath, but the uimaTypeAndFeat can have : at the end
			String uimaType = uimaTypeAndFeat.replace(":", "");
			if (ann.getType().getName().compareTo(uimaType) == 0) {
				featurePathValueList.add(ann.getCoveredText());
				return featurePathValueList;
			} else
				return null;
		}
		// there is a feature path, so split the single component of the path (feature) in an array
		String[] featurePathElementNamesArray = typeAndFeatArray[1].split("/");

		return getValueOfFeature(ann, featurePathElementNamesArray, 0);
	}

	private List<String> getValueOfFeature(FeatureStructure featStruct, String[] path, int posFeatPath) {
		if (posFeatPath > path.length)
			return null;
		if (featStruct == null)
			return null;
		String featName = path[posFeatPath];
		Feature feature = featStruct.getType().getFeatureByBaseName(featName.split("\\[")[0]);

		if (feature == null)
			return null;

		// check if we are analyzing the last element of the array and it is a primitive.
		// In this case return it, otherwise return null
		if (path.length == posFeatPath + 1) {
			if (feature.getRange().isPrimitive()) {
				List<String> valueList = new ArrayList<String>();
				String value = featStruct.getFeatureValueAsString(feature);
				valueList.add(value);
				return valueList;
			} else
				return null;
		}

		// check if the uimaType is an Array
		if (feature.getRange().isArray()) {
			boolean posSpecified = false;
			int posList = -1;
			if (featName.contains("[")) {
				posList = Integer.parseInt(featName.split("\\[")[1].split("\\]")[0]);
				posSpecified = true;
			}
			return getValueOfFeatureFromArray(featStruct.getFeatureValue(feature), posFeatPath, path,
					posSpecified, posList);
		}
		// check if the uimaType is a List
		else if (feature.getRange().getName().startsWith("uima.cas")
				&& feature.getRange().getName().endsWith("List")) {
			boolean posSpecified = false;
			int posList = -1;
			if (featName.contains("[")) {
				posList = Integer.parseInt(featName.split("\\[")[1].split("\\]")[0]);
				posSpecified = true;
			}
			return getValueOfFeatureFromList(featStruct.getFeatureValue(feature), posFeatPath, path,
					posSpecified, posList);
		}
		// the featureStruct is not a List nor an Array, so it must be a complex one
		List<String> stringValueArray = new ArrayList<String>();
		List<String> valueList = getValueOfFeature(featStruct.getFeatureValue(feature), path, posFeatPath + 1);
		if (valueList == null)
			return null;
		stringValueArray.addAll(valueList);
		return stringValueArray;

	}

	private List<String> getValueOfFeatureFromList(FeatureStructure featStruct, int posFeatPath,
			String[] path, boolean posSpecified, int posList) {
		if (featStruct == null)
			return null;
		Type uimaType = featStruct.getType();
		if ((uimaType.getName().compareTo(NONEMPTYFSLIST) == 0)) {
			FSList fsList = (FSList) featStruct;
			if (posSpecified) {
				FeatureStructure nextFeatStruct = getElemFromListAtPos(fsList, posList);
				if (nextFeatStruct == null)
					return null;
				return getValueOfFeature(nextFeatStruct, path, posFeatPath + 1);
			} else {
				List<String> valueList = new ArrayList<String>();

				List<FeatureStructure> featStructList = getAllElemFromList(fsList);
				for (FeatureStructure featStructFromList : featStructList) {
					List<String> valueListTemp = getValueOfFeature(featStructFromList, path, posFeatPath + 1);
					valueList.addAll(valueListTemp);
				}
				return valueList;
			}
		} else if ((uimaType.getName().compareTo(NONEMPTYSTRINGLIST) == 0)) {
			StringList stringList = (StringList) featStruct;
			if (posFeatPath >= path.length)
				return null;
			if (posSpecified) {
				String value = getElemFromListAtPos(stringList, posList);
				if (value == null)
					return null;
				List<String> valueList = new ArrayList<String>();
				valueList.add(value);
				return valueList;
			} else {
				return getAllElemFromList(stringList);
			}
		}
		return null;

	}

	private FeatureStructure getElemFromListAtPos(FSList fsList, int pos) {
		FSList tempList = fsList;
		for (int i = 0; i < pos; ++i) {
			if (tempList instanceof NonEmptyFSList)
				tempList = ((NonEmptyFSList) tempList).getTail();
			else
				return null;
		}
		if (tempList instanceof NonEmptyFSList)
			return ((NonEmptyFSList) tempList).getHead();
		else
			return null;
	}

	private List<FeatureStructure> getAllElemFromList(FSList fsList) {
		FSList tempFSList = fsList;
		List<FeatureStructure> featStructList = new ArrayList<FeatureStructure>();
		while (true) {
			if (tempFSList instanceof NonEmptyFSList) {
				featStructList.add(((NonEmptyFSList) tempFSList).getHead());
				tempFSList = ((NonEmptyFSList) tempFSList).getTail();
			} else
				break;
		}
		return featStructList;
	}

	private String getElemFromListAtPos(StringList stringList, int pos) {
		StringList tempList = stringList;
		for (int i = 0; i < pos; ++i) {
			if (tempList instanceof NonEmptyStringList)
				tempList = ((NonEmptyStringList) tempList).getTail();
			else
				return null;
		}
		if (tempList instanceof NonEmptyStringList)
			return ((NonEmptyStringList) tempList).getHead();
		else
			return null;
	}

	private List<String> getAllElemFromList(StringList stringList) {
		StringList tempStringList = stringList;
		List<String> listOfString = new ArrayList<String>();
		while (true) {
			if (tempStringList instanceof NonEmptyStringList) {
				listOfString.add(((NonEmptyStringList) tempStringList).getHead());
				tempStringList = ((NonEmptyStringList) tempStringList).getTail();
			} else
				break;
		}
		return listOfString;
	}

	private List<String> getValueOfFeatureFromArray(FeatureStructure featStruct, int posFeatPath,
			String[] path, boolean posSpecified, int posArray) {
		Type uimaType = featStruct.getType();
		List<String> valueList = new ArrayList<String>();
		if (uimaType.getName().compareTo(INTEGERARRAY) == 0) {
			IntegerArray intArray = (IntegerArray) featStruct;
			if (posFeatPath >= path.length)
				return null;
			if (posSpecified) {
				if (intArray.size() > posArray) {
					valueList.add("" + intArray.get(posArray));
				} else
					return null;
			} else {
				for (int i = 0; i < intArray.size(); ++i)
					valueList.add("" + intArray.get(i));
			}
		} else if (uimaType.getName().compareTo(BOOLEANARRAY) == 0) {
			BooleanArray booleanArray = (BooleanArray) featStruct;
			if (posFeatPath >= path.length)
				return null;
			if (posSpecified) {
				if (booleanArray.size() > posArray) {
					valueList.add("" + booleanArray.get(posArray));
				} else
					return null;
			} else {
				for (int i = 0; i < booleanArray.size(); ++i)
					valueList.add("" + booleanArray.get(i));
			}
		} else if (uimaType.getName().compareTo(LONGARRAY) == 0) {
			LongArray longArray = (LongArray) featStruct;
			if (posFeatPath >= path.length)
				return null;
			if (posSpecified) {
				if (longArray.size() > posArray) {
					valueList.add("" + longArray.get(posArray));
				} else
					return null;
			} else {
				for (int i = 0; i < longArray.size(); ++i)
					valueList.add("" + longArray.get(i));
			}
		} else if (uimaType.getName().compareTo(DOUBLEARRAY) == 0) {
			DoubleArray doubleArray = (DoubleArray) featStruct;
			if (posFeatPath >= path.length)
				return null;
			if (posSpecified) {
				if (doubleArray.size() > posArray) {
					valueList.add("" + doubleArray.get(posArray));
				} else
					return null;
			} else {
				for (int i = 0; i < doubleArray.size(); ++i)
					valueList.add("" + doubleArray.get(i));
			}
		} else if (uimaType.getName().compareTo(FLOATARRAY) == 0) {
			FloatArray floatArray = (FloatArray) featStruct;
			if (posFeatPath >= path.length)
				return null;
			if (posSpecified) {
				if (floatArray.size() > posArray) {
					valueList.add("" + floatArray.get(posArray));
				} else
					return null;
			} else {
				for (int i = 0; i < floatArray.size(); ++i)
					valueList.add("" + floatArray.get(i));
			}
		} else if (uimaType.getName().compareTo(STRINGARRAY) == 0) {
			StringArray stringArray = (StringArray) featStruct;
			if (posFeatPath >= path.length)
				return null;
			if (posSpecified) {
				if (stringArray.size() > posArray) {
					valueList.add("" + stringArray.get(posArray));
				} else
					return null;
			} else {
				for (int i = 0; i < stringArray.size(); ++i)
					valueList.add("" + stringArray.get(i));
			}
		} else { // the array has not primitive value
			FSArray fsArray = (FSArray) featStruct;
			if (posSpecified) {
				if (fsArray.size() > posArray) {
					return getValueOfFeature(fsArray.get(posArray), path, posFeatPath + 1);
				} else
					return null;
			} else {
				for (int i = 0; i < fsArray.size(); ++i) {
					List<String> valueListTemp = getValueOfFeature(fsArray.get(i), path, posFeatPath + 1);
					if (valueListTemp != null)
						valueList.addAll(valueListTemp);
				}
			}
		}
		return valueList;
	}

	private List<String> getValueOfFeatureGeneric(FeatureStructure featStruct, int posFeatPath, String[] path) {
		Type uimaType = featStruct.getType();

		return null;

	}
}
